package com.java.base.io.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalTime;
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class BIOServer {


    int port;
    ServerSocket serverSocket;

    int maxConn;
    ThreadPoolExecutor threadPoolExecutor;
    Socket[] sockets;
    LinkedBlockingQueue<Integer> freeSite;
    LinkedList<Integer> useSite;
    byte[][] bytes;

    public BIOServer(int port, int maxConn) {
        this.port = port;
        this.maxConn = maxConn;
        threadPoolExecutor = new ThreadPoolExecutor(maxConn, maxConn, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        bytes = new byte[maxConn][1024];

        sockets = new Socket[maxConn];
        freeSite = new LinkedBlockingQueue<>();
        useSite = new LinkedList<>();

        for (int i = 0; i < maxConn; i++) {
            freeSite.add(i);
        }

    }

    public void start() throws IOException {

        serverSocket = new ServerSocket(port);

        while (!serverSocket.isClosed()) {
            try {
                Socket socket = serverSocket.accept();
                Integer i = freeSite.poll();
                if (i == null) {
                    socket.close();
                    continue;
                }
                sockets[i] = socket;
                useSite.offer(i);
                accept(socket, i);
            } catch (IOException e) {
                System.err.println(e.getMessage());
            }
        }

    }


    void accept(Socket socket, int site) throws IOException {
        byte[] bs = bytes[site];

        threadPoolExecutor.execute(() -> {
            try {
                int i;
                while (!socket.isClosed()) {
                    i = socket.getInputStream().read(bs);
                    send(site, i);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                bye(site);
            }
        });

    }

    void send(int site, int i) {
        for (Integer j : useSite) {
            if (j == null) {
                continue;
            }
            Socket socket = sockets[j];
            if (socket == null || socket.isClosed()) {
                continue;
            }

            try {
                socket.getOutputStream().write(bytes[site], 0, i);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void bye(int site) {
        useSite.remove(site);
        Socket socket = sockets[site];
        sockets[site] = null;
        if (socket != null && !socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        freeSite.add(site);

    }

    public void stop() throws IOException {
        serverSocket.close();
    }


    public static void main(String[] args) throws IOException, InterruptedException {
        BIOServer bioServer = new BIOServer(0xaaaa, 4);
        new Thread(() -> {
            try {
                bioServer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        StringBuilder sb;
        Thread.sleep(1000);
        while (!bioServer.serverSocket.isClosed()) {
            Thread.sleep(2000);
            sb = new StringBuilder(LocalTime.now().toString()).append("\r\n");
            sb.append("free:").append(bioServer.freeSite.size()).append("\r\n");
            sb.append("use:").append(bioServer.useSite.size()).append("\r\n");

            for (int i = 0; i < bioServer.maxConn; i++) {
                Socket socket = bioServer.sockets[i];
                if (socket == null || socket.isClosed()) {
                    sb.append("_");
                } else {
                    sb.append("x");
                }
            }
            sb.append("\r\n");
            System.err.println(sb);
        }
    }


}
